<?php
/**
 * DependencyInjector class definition
 *  
 * @author Sean Crystal <seancrystal@gmail.com>
 * @package MutantVole
 */     
 require_once 'components/Contract.php';
 
/**
 * A simple dependency injector class to implement the inversion of control pattern. 
 * @package MutantVole
 * @subpackage Base
 */   
class Injector {
	
	/**
     * namespace to use for singleton objects
	 */ 	
    const SINGLETON = 'SINGLETON';

	/**
     * Binds an abstract interface name to a concrete class name within a namespace. The concrete
     * class must exist. 
	 * @param string $abstract
	 * @param string $concrete
	 * @param string $namespace
	 */   
    public static function Bind($abstract, $concrete, $namespace = 'DEFAULT') {
        Contract::Precondition(class_exists($concrete), '$concrete must be a valid class name');
				
		self::$bindings[$namespace][$abstract] = $concrete;
    }

	/**
     * Gets a new instance of the concrete class bound to an interface
	 * @param string $abstract
	 * @param string $namespace
	 * @return mixed 
	 */   	
    public static function &GetInstance($abstract, $namespace = 'DEFAULT') {
		if (!(self::Bound($abstract, $namespace))) {
			throw new UnboundDependencyException($abstract, $namespace);
		}

        $class = self::$bindings[$namespace][$abstract];

        return new $class();
    }

	/**
     * Gets a singleton instance of a concrete class bound to an interface. The injector will create the singleton
	 * instance if one does not already exist.
	 * @param string $abstract
	 * @return mixed 
	 */   	
    public static function &GetSingleton($abstract) {
		if (!(self::Bound($abstract, self::SINGLETON))) {
			throw new UnboundDependencyException($abstract, self::SINGLETON);
		}

		$class = self::$bindings[self::SINGLETON][$abstract];
		
		if (!(self::$singletons[$abstract])) {
            self::$singletons[$abstract] = new $class;
        }

        return self::$singletons[$abstract];
    }
    
    /**
     * Checks whether an abstract interface is bound
	 * @param string $abstract
	 * @param string $namespace
	 * @return bool
	 */     
    public static function Bound($abstract, $namespace = 'DEFAULT') {
		return (is_array(self::$bindings[$namespace]) && array_key_exists($abstract, self::$bindings[$namespace]));
	}

	/**
     * contains the names of all the bound interfaces and classes
     * @var array 
	 */ 	
    private static $bindings = array();
    
    /**
     * contains the singleton instances
	 * @var array
	 */   
    private static $singletons = array();
}

?>